package org.papervision3d.objects
{
    import org.papervision3d.Papervision3D;
    
    import org.papervision3d.core.*;
    import org.papervision3d.core.proto.*;
    import org.papervision3d.core.geom.DrawTriangle;
    import org.papervision3d.materials.MaterialsList;
    
    import flash.display.Sprite;
    import flash.utils.Dictionary;
    
    // The DisplayObject class represents instances of 3D objects that are contained in the scene.
    // That includes all objects in the scene, not only those that can be rendered, but also the camera and its target.
    // The DisplayObject3D class supports basic functionality like the x, y and z position of an object, as well as rotationX, rotationY, rotationZ, scaleX, scaleY and scaleZ and visible. It also supports more advanced properties of the object such as its transform Matrix3D.
    // DisplayObject3D is not an abstract base class; therefore, you can call DisplayObject3D directly. Invoking new DisplayObject() creates a new empty object in 3D space, like when you used createEmptyMovieClip().
    public class DisplayObject3D extends DisplayObjectContainer3D
    {
        // An Number that sets the X coordinate of a object relative to the scene coordinate system.
        public function get x():Number
        {
            return this.transform.n14;
        }
    
        public function set x(value:Number):void
        {
            this.transform.n14 = value;
        }
    
        // An Number that sets the Y coordinate of a object relative to the scene coordinates.
        public function get y():Number
        {
            return this.transform.n24;
        }
    
        public function set y(value:Number):void
        {
            this.transform.n24 = value;
        }
    
        // An Number that sets the Z coordinate of a object relative to the scene coordinates.
        public function get z():Number
        {
            return this.transform.n34;
        }
    
        public function set z(value:Number):void
        {
            this.transform.n34 = value;
        }
    
        // Specifies the rotation around the X axis from its original orientation.
        public function get rotationX():Number
        {
            if (this._rotationDirty) 
                updateRotation();
    
            return Papervision3D.useDEGREES ? -this._rotationX * toDEGREES : -this._rotationX;
        }
    
        public function set rotationX(rot:Number):void
        {
            this._rotationX = Papervision3D.useDEGREES ? -rot * toRADIANS : -rot;
            this._transformDirty = true;
        }
    
        // Specifies the rotation around the Y axis from its original orientation.
        public function get rotationY():Number
        {
            if (this._rotationDirty) 
                updateRotation();
    
            return Papervision3D.useDEGREES ? -this._rotationY * toDEGREES : -this._rotationY;
        }
    
        public function set rotationY(rot:Number):void
        {
            this._rotationY = Papervision3D.useDEGREES ? -rot * toRADIANS : -rot;
            this._transformDirty = true;
        }
    
        // Specifies the rotation around the Z axis from its original orientation.
        public function get rotationZ():Number
        {
            if (this._rotationDirty) 
                updateRotation();
    
            return Papervision3D.useDEGREES ? -this._rotationZ * toDEGREES : -this._rotationZ;
        }
    
        public function set rotationZ(rot:Number):void
        {
            this._rotationZ = Papervision3D.useDEGREES ? -rot * toRADIANS : -rot;
            this._transformDirty = true;
        }
        
        
    
        // Update rotation values
        private function updateRotation():void
        {
            var rot:Number3D = Matrix3D.matrix2euler(this.transform);
            this._rotationX = rot.x * toRADIANS;
            this._rotationY = rot.y * toRADIANS;
            this._rotationZ = rot.z * toRADIANS;
    
            this._rotationDirty = false;
        }
    
        public function set scale(scale:Number):void
        {
            if (Papervision3D.usePERCENT) 
                scale /= 100;
    
            this._scaleX = this._scaleY = this._scaleZ = scale;
    
            this._transformDirty = true;
        }
    
        public function set scaleX(scale:Number):void
        {
            if (Papervision3D.usePERCENT) 
                this._scaleX = scale / 100;
            else 
                this._scaleX = scale;
    
            this._transformDirty = true;
        }
    
        public function set scaleY(scale:Number):void
        {
            if (Papervision3D.usePERCENT) 
                this._scaleY = scale / 100;
            else 
                this._scaleY = scale;
    
            this._transformDirty = true;
        }
    
        public function set scaleZ(scale:Number):void
        {
            if (Papervision3D.usePERCENT) 
                this._scaleZ = scale / 100;
            else 
                this._scaleZ = scale;
    
            this._transformDirty = true;
        }
        
         /**
        * Add by pil
        * 
        * allow or prevent Mesh3D to dispatch MouseEvent3D
        */
        
        private var _enabled : Boolean = true;
        
        override public function set enabled ( flag : Boolean ) : void {
        	_enabled = flag;
        }
        override public function get enabled () : Boolean {
        	return _enabled;
        }
        
    
        // Whether or not the display object is visible.
        // A Boolean value that indicates whether the object is projected, transformed and rendered. A value of false will effectively ignore the object. The default value is true.
        public var visible:Boolean;
    
        // An optional object name.
        public var name:String;
    
    	// parent
    	public var parent : DisplayObjectContainer3D;
    	
        // [read-only] Unique id of this instance.
        public var id:int;
    
        // The list of materials for this instance.
        public var materials:MaterialsList;
    	
    	// decalage de Z, pour le zSorting
    	/**
        * Add by pil
        * 
        * add a Zoffset a Zdepths of face3D to prevent z-sorting artefacts
        */
    	public var Zoffset : Number = 0;
    	
        // An object that contains user defined properties.
        // All properties of the extra field are copied into the new instance. The properties specified with extra are publicly available.
        public var extra:Object; // = {}; TBD
    
        // The Sprite that you draw into when rendering in a MovieScene3D.
        // While Scene3D uses a single Sprite container for all the objects, MovieScene3D renders each object in its own unique Sprite.
        // You can use the container like any other Sprite. For example, you can add events to it, apply filters or change the blend mode.
        public var container:Sprite;
    
        // Returns an empty DiplayObject3D object positioned in the center of the 3D coordinate system (0, 0 ,0).
        static public function get ZERO():DisplayObject3D
        {
            return new DisplayObject3D();
        }
    
        // Relative directions.
        static private var FORWARD :Number3D = new Number3D( 0,  0,  1);
        static private var BACKWARD:Number3D = new Number3D( 0,  0, -1);
        static private var LEFT    :Number3D = new Number3D(-1,  0,  0);
        static private var RIGHT   :Number3D = new Number3D( 1,  0,  0);
        static protected var UP      :Number3D = new Number3D( 0,  1,  0);
        static private var DOWN    :Number3D = new Number3D( 0, -1,  0);
    
        // A Matrix3D object containing values that affect the scaling, rotation, and translation of the display object.
        public var transform:Matrix3D;
    
        // [internal-use] A camera transformed Matrix3D object.
        public var view:Matrix3D;
    
        // The GeometryObject3D object that contains the 3D definition of this instance.
        // When different objects share the same geometry, they become instances. They are the same object, displayed multiple times. Changing the shape of this object changes the shape of all of its instances.
        // Instancing an object saves system memory, and is useful to display an object multiple times while maintaining its shape.
        // For example, you could create armies and forests full of duplicate objects without needing the memory to handle that much actual geometry. Each instance has its own transform node so it can have its own position, rotation, and scaling.
//        public var geometry:GeometryObject3D;
    
        // Creates a new DisplayObject3D instance. After creating the instance, call the addChild() method of a DisplayObjectContainer3D.
        // @param name        [optional] - The name of the newly created object.
        // @param geometry    [optional] - The geometry of the newly created object.
        // @param initObject  [optional] - An object that contains user defined properties with which to populate the newly created DisplayObject3D.
        // x: An Number that sets the X coordinate of a object relative to the scene coordinate system.
        // y: An Number that sets the Y coordinate of a object relative to the scene coordinate system.
        // z: An Number that sets the Z coordinate of a object relative to the scene coordinate system.
        // rotationX: Specifies the rotation around the X axis from its original orientation.
        // rotationY: Specifies the rotation around the Y axis from its original orientation.
        // rotationZ: Specifies the rotation around the Z axis from its original orientation.
        // scaleX: Sets the scale along the local X axis as applied from the registration point of the object.
        // scaleY: Sets the scale along the local Y axis as applied from the registration point of the object.
        // scaleZ: Sets the scale along the local Z axis as applied from the registration point of the object.
        // visible: Whether or not the display object is visible.
        // A Boolean value that indicates whether the object is projected, transformed and rendered. A value of false will effectively ignore the object. The default value is true.
        // container: The MovieClip that you draw into when rendering. Use only when the object is rendered in its own unique MovieClip.
        // It's Boolean value determines whether the container MovieClip should be cleared before rendering.
        // extra: An object that contains user defined properties.
        // All properties of the extra field are copied into the new instance. The properties specified with extra are publicly available.
        public function DisplayObject3D(name:String = null, initObject:Object = null):void
        {
            super();
    
            Papervision3D.log("DisplayObject3D: " + name);
    
            this.transform = Matrix3D.IDENTITY;
            this.view      = Matrix3D.IDENTITY;
    
            // TODO if (initObject)...
            this.x = initObject ? initObject.x || 0 : 0;
            this.y = initObject ? initObject.y || 0 : 0;
            this.z = initObject ? initObject.z || 0 : 0;
    
            rotationX = initObject ? initObject.rotationX || 0 : 0;
            rotationY = initObject ? initObject.rotationY || 0 : 0;
            rotationZ = initObject ? initObject.rotationZ || 0 : 0;
    
            var scaleDefault:Number = Papervision3D.usePERCENT? 100 : 1;
            scaleX = initObject ? initObject.scaleX || scaleDefault : scaleDefault;
            scaleY = initObject ? initObject.scaleY || scaleDefault : scaleDefault;
            scaleZ = initObject ? initObject.scaleZ || scaleDefault : scaleDefault;
    
            if (initObject && initObject.extra) 
                this.extra = initObject.extra;
            if (initObject && initObject.container) 
                this.container = initObject.container;
    
            this.visible = true;
    
            this.id = _totalDisplayObjects++;
            this.name = name || String(this.id);
        }
    
        // Gets the distance to the position of the given object.
        // @param obj The display object to measure the distance to.
        // @return The distance to the registration point of the given object.
        public function distanceTo(obj:DisplayObject3D):Number
        {
            var x:Number = this.x - obj.x;
            var y:Number = this.y - obj.y;
            var z:Number = this.z - obj.z;
    
            return Math.sqrt(x*x + y*y + z*z);
        }
    
        // Returns the material that exists with the specified name in the materials list.
        // If more that one material object has the specified name, the method returns the first material object in the materials list.
        // @param    name    The name of the material to return.
        // @return   The material object with the specified name.
        // TODO: Recursive
        public function getMaterialByName(name:String):MaterialObject3D
        {
            var material:MaterialObject3D = this.materials.getMaterialByName(name);
    
            if (material)
                return material;
            else
                for each (var child:DisplayObject3D in this._childrenByName)
                {
                    material = child.getMaterialByName(name);
    
                    if (material) 
                        return material;
                }
    
            return null;
        }

        // [internal-use] Projects three dimensional coordinates onto a two dimensional plane to simulate the relationship of the camera to subject.
        // This is the first step in the process of representing three dimensional shapes two dimensionally.
        // @param    parent  The DisplayObject3D object that contains this display object.
        // @param    camera  Camera3D object to render from.
        // @param    sorted  The list of faces of the current sort branch.
        public function project(parent:DisplayObject3D, camera:CameraObject3D, callback:TraverseCallback):void//Number
        {
            updateTransform();
            
            _concatZoffset = parent._concatZoffset + Zoffset;
            //_concatAlpha = parent._concatAlpha * _alpha;
			
            this.view = Matrix3D.multiply(parent.view, this.transform); // TODO: OPTIMIZE (MED) Inline this

            for each (var child:DisplayObject3D in this._childrenByName)
                if (callback.enter(child))
                {
                    child.project(this, camera, callback);
                    callback.leave(child);
                }
        }
        
        
    	public function dispose() : void {
    		for each (var child:DisplayObject3D in this._childrenByName)
               child.dispose();
              
            if( _sorted ) while( _sorted.length > 0 ) _sorted.shift();
              
            
            if ( parent )parent.removeChild( this );
            if( materials ) 
            	for ( var key : * in materials.materialsByName )
            		delete materials.materialsByName[key];
            
            transform = null;
            view = null;
    	}
       
    
        // Translate the display object in the direction it is facing, i.e. it's positive Z axis.
        // @param    distance    The distance that the object should move forward.
        public function moveForward(distance:Number):void 
        { 
            translate(distance, FORWARD); 
        }
    
        // Translate the display object in the opposite direction it is facing, i.e. it's negative Z axis.
        // @param    distance    The distance that the object should move backward.
        public function moveBackward(distance:Number):void 
        { 
            translate(distance, BACKWARD); 
        }
    
        // Translate the display object lateraly, to the left of the direction it is facing, i.e. it's negative X axis.
        // @param distance The distance that the object should move left.
        public function moveLeft(distance:Number):void 
        { 
            translate(distance, LEFT); 
        }
    
        // Translate the display object lateraly, to the right of the direction it is facing, i.e. it's positive X axis.
        // @param distance The distance that the object should move right.
        public function moveRight(distance:Number):void 
        { 
            translate(distance, RIGHT); 
        }
    
        // Translate the display object upwards, with respect to the direction it is facing, i.e. it's positive Y axis.
        // @param distance The distance that the object should move up.
        public function moveUp(distance:Number):void 
        { 
            translate(distance, UP); 
        }
    
        // Translate the display object downwards, with respect to the direction it is facing, i.e. it's negative Y axis.
        // @param distance The distance that the object should move down.
        public function moveDown(distance:Number):void 
        { 
            translate(distance, DOWN); 
        }
    
        // Move the object along a given direction.
        // @param distance The distance that the object should travel.
        // @param axis The direction that the object should move towards.
        public function translate(distance:Number, axis:Number3D):void
        {
            var vector:Number3D = axis.clone();
    
            updateTransform();
    
            Matrix3D.rotateAxis(transform, vector)
    
            this.x += distance * vector.x;
            this.y += distance * vector.y;
            this.z += distance * vector.z;
        }
    
        // Rotate the display object around its lateral or transverse axis —an axis running from the pilot's left to right in piloted aircraft, and parallel to the wings of a winged aircraft; thus the nose pitches up and the tail down, or vice-versa.
        // @param angle The angle to rotate.
        public function pitch(angle:Number):void
        {
            angle = Papervision3D.useDEGREES ? angle * toRADIANS : angle;
    
            var vector:Number3D = RIGHT.clone();
    
            updateTransform();
    
            Matrix3D.rotateAxis(transform, vector);
            var m:Matrix3D = Matrix3D.rotationMatrix(vector.x, vector.y, vector.z, angle);
    
            this.transform.copy3x3(Matrix3D.multiply3x3(m ,transform));
    
            this._rotationDirty = true;
        }

        // Rotate the display object around about the vertical axis —an axis drawn from top to bottom.
        // @param angle The angle to rotate.
        public function yaw(angle:Number):void
        {
            angle = Papervision3D.useDEGREES ? angle * toRADIANS : angle;
    
            var vector:Number3D = UP.clone();
    
            updateTransform();
    
            Matrix3D.rotateAxis(transform, vector);
            var m:Matrix3D = Matrix3D.rotationMatrix(vector.x, vector.y, vector.z, angle);
    
            this.transform.copy3x3(Matrix3D.multiply3x3(m ,transform));
    
            this._rotationDirty = true;
        }
    
        // Rotate the display object around the longitudinal axis —an axis drawn through the body of the vehicle from tail to nose in the normal direction of flight, or the direction the object is facing.
        // @param angle
        public function roll(angle:Number):void
        {
            angle = Papervision3D.useDEGREES ? angle * toRADIANS : angle;
    
            var vector:Number3D = FORWARD.clone();
    
            updateTransform();
    
            Matrix3D.rotateAxis(transform, vector);
            var m:Matrix3D = Matrix3D.rotationMatrix(vector.x, vector.y, vector.z, angle);
    
            this.transform.copy3x3(Matrix3D.multiply3x3(m ,transform));
    
            this._rotationDirty = true;
        }
    
        // Make the object look at a specific position.
        // @param targetObject Object to look at.
        // @param upAxis The vertical axis of the universe. Normally the positive Y axis.
        public function lookAt(targetObject:DisplayObject3D, upAxis:Number3D = null):void
        {
            var position:Number3D = new Number3D(this.x, this.y, this.z);
            var target:Number3D = new Number3D(targetObject.x, targetObject.y, targetObject.z);
    
            var zAxis:Number3D = Number3D.sub(target, position);
            zAxis.normalize();
    
            if (zAxis.modulo > 0.1)
            {
                var xAxis:Number3D = Number3D.cross(zAxis, upAxis || UP);
                xAxis.normalize();
    
                var yAxis:Number3D = Number3D.cross(zAxis, xAxis);
                yAxis.normalize();
    
                var look:Matrix3D = this.transform;
    
                look.n11 = xAxis.x;
                look.n21 = xAxis.y;
                look.n31 = xAxis.z;
    
                look.n12 = -yAxis.x;
                look.n22 = -yAxis.y;
                look.n32 = -yAxis.z;
    
                look.n13 = zAxis.x;
                look.n23 = zAxis.y;
                look.n33 = zAxis.z;
    
                this._transformDirty = false;
                this._rotationDirty = true;
                // TODO: Implement scale
            }
            else
            {
                //var log:XrayLog = new XrayLog();
                //log.debug("lookAt Error");
                throw new Error("lookAt Error");
            }
        }
        
        
        public function lookAtNumber(zAxis : Number3D , upAxis:Number3D = null):void
        {
            zAxis.normalize();
    
            if (zAxis.modulo > 0.1)
            {
                var xAxis:Number3D = Number3D.cross(zAxis, upAxis || UP);
                xAxis.normalize();
    
                var yAxis:Number3D = Number3D.cross(zAxis, xAxis);
                yAxis.normalize();
    
                var look:Matrix3D = this.transform;
    
                look.n11 = xAxis.x;
                look.n21 = xAxis.y;
                look.n31 = xAxis.z;
    
                look.n12 = -yAxis.x;
                look.n22 = -yAxis.y;
                look.n32 = -yAxis.z;
    
                look.n13 = zAxis.x;
                look.n23 = zAxis.y;
                look.n33 = zAxis.z;
    
                this._transformDirty = false;
                this._rotationDirty = true;
                // TODO: Implement scale
            }
            else
            {
                //var log:XrayLog = new XrayLog();
                //log.debug("lookAt Error");
                throw new Error("lookAt Error");
            }
        }
        
        
    
        // Copies the position information (x, y and z coordinates) from another object or Matrix3D.
        // @param reference A DisplayObject3D or Matrix3D object to copy the position from.
        public function copyPosition(reference:*):void
        {
            var trans:Matrix3D = this.transform;
            var matrix:Matrix3D = (reference is DisplayObject3D) ? reference.transform : reference;
    
            trans.n14 = matrix.n14;
            trans.n24 = matrix.n24;
            trans.n34 = matrix.n34;
        }
    
        // Copies the transformation information (position, rotation and scale) from another object or Matrix3D.
        // @param reference A DisplayObject3D or Matrix3D object to copy the position from.
        public function copyTransform(reference:*):void
        {
            var trans:Matrix3D = this.transform;
            var matrix:Matrix3D = (reference is DisplayObject3D) ? reference.transform : reference;
    
            trans.n11 = matrix.n11;   trans.n12 = matrix.n12;
            trans.n13 = matrix.n13;   trans.n14 = matrix.n14;
    
            trans.n21 = matrix.n21;   trans.n22 = matrix.n22;
            trans.n23 = matrix.n23;   trans.n24 = matrix.n24;
    
            trans.n31 = matrix.n31;   trans.n32 = matrix.n32;
            trans.n33 = matrix.n33;   trans.n34 = matrix.n34;
    
            this._transformDirty = false;
            this._rotationDirty = true;
        }
        
        // [internal-use] Updates the transform Matrix3D with the current rotation and scale values.
        // TODO OPTIMIZE (HIGH)
        protected function updateTransform():void
        {
            if (!this._transformDirty) 
                return;

            var q:Quaternion = Matrix3D.euler2quaternion(-this._rotationY, -this._rotationZ, this._rotationX); // Swapped
    
            var m:Matrix3D = Matrix3D.quaternion2matrix(q.x, q.y, q.z, q.w);
    
            var transform:Matrix3D = this.transform;
    
            m.n14 = transform.n14;
            m.n24 = transform.n24;
            m.n34 = transform.n34;
    
            transform.copy(m);
    
            // Scale
            var scaleM:Matrix3D = Matrix3D.IDENTITY;
            scaleM.n11 = this._scaleX;
            scaleM.n22 = this._scaleY;
            scaleM.n33 = this._scaleZ;
    
            this.transform = Matrix3D.multiply(transform, scaleM);
    
            this._transformDirty = false;
        }
    
        // Returns a string value representing the three-dimensional position values of the display object instance.
        // @return   A string.
        public override function toString():String
        {
            return this.name + ': x:' + Math.round(this.x) + ' y:' + Math.round(this.y) + ' z:' + Math.round(this.z);
        }

        // [internal-use]
        protected var _transformDirty:Boolean = false;
    
        private var _rotationX:Number;
        private var _rotationY:Number;
        private var _rotationZ:Number;
        private var _rotationDirty:Boolean = false;
    
        private var _scaleX:Number;
        private var _scaleY:Number;
        private var _scaleZ:Number;
        //private var _scaleDirty:Boolean = false;
        
        public var _concatZoffset : Number = 0;
        public var _alpha : Number = 1;
        public var _concatAlpha : Number = 1;
    
        protected var _sorted:Array;
    
        static private var _totalDisplayObjects:int = 0;
    
        static private var toDEGREES:Number = 180 / Math.PI;
        static private var toRADIANS:Number = Math.PI / 180;
    }
}
